package com.chj.dfs.class04;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;

//https://leetcode-cn.com/problems/combination-sum/solution/di-gui-hui-su-tu-wen-fen-xi-ji-bai-liao-9987de-yon/
public class Code08_CombinationSum {

	public static List<List<Integer>> combinationSum(int[] candidates, int target) {
		int len = candidates.length;
		List<List<Integer>> res = new ArrayList<>();
		if (len == 0) {
			return res;
		}

		Deque<Integer> path = new ArrayDeque<>();
		dfs(candidates, 0, len, target, path, res);
		return res;
	}

	/**
	 * @param candidates 候选数组
	 * @param begin      搜索起点
	 * @param len        冗余变量，是 candidates 里的属性，可以不传
	 * @param target     每减去一个元素，目标值变小
	 * @param path       从根结点到叶子结点的路径，是一个栈
	 * @param res        结果集列表
	 */
	private static void dfs(int[] candidates, int begin, int len, int target, Deque<Integer> path,
			List<List<Integer>> res) {
		// target 为负数和 0 的时候不再产生新的孩子结点
		if (target < 0) {
			return;
		}
		if (target == 0) {
			res.add(new ArrayList<>(path));
			return;
		}

		// 重点理解这里从 begin 开始搜索的语意
		for (int i = begin; i < len; i++) {
			path.addLast(candidates[i]);

			// 注意：由于每一个元素可以重复使用，下一轮搜索的起点依然是 i，这里非常容易弄错
			dfs(candidates, i, len, target - candidates[i], path, res);

			// 状态重置
			path.removeLast();
		}
	}

	public static List<List<Integer>> combinationSum02(int[] candidates, int target) {
		int len = candidates.length;
		List<List<Integer>> res = new ArrayList<>();
		if (len == 0) {
			return res;
		}

		// 排序是剪枝的前提
		Arrays.sort(candidates);
		Deque<Integer> path = new ArrayDeque<>();
		dfs02(candidates, 0, len, target, path, res);
		return res;
	}

	private static void dfs02(int[] candidates, int begin, int len, int target, Deque<Integer> path,
			List<List<Integer>> res) {
		// 由于进入更深层的时候，小于 0 的部分被剪枝，因此递归终止条件值只判断等于 0 的情况
		if (target == 0) {
			res.add(new ArrayList<>(path));
			return;
		}

		for (int i = begin; i < len; i++) {
			// 重点理解这里剪枝，前提是候选数组已经有序，
			if (target - candidates[i] < 0) {
				break;
			}

			path.addLast(candidates[i]);
			dfs02(candidates, i, len, target - candidates[i], path, res);
			path.removeLast();
		}
	}

	public static List<List<Integer>> combinationSum03(int[] candidates, int target) {
		List<List<Integer>> result = new ArrayList<>();
		getResult03(result, new ArrayList<>(), candidates, target, 0);
		return result;
	}

	private static void getResult03(List<List<Integer>> result, List<Integer> cur, int candidates[], int target,
			int start) {
		if (target == 0) {
			result.add(new ArrayList<>(cur));
			return;
		}
		for (int i = start; i < candidates.length; i++) {
			if (target < candidates[i])
				continue;
			// 选择当前节点，类似于从当前节点开始往下遍历
			cur.add(candidates[i]);
			getResult03(result, cur, candidates, target - candidates[i], i);
			// 回到当前节点的时候我们把当前节点给移除,
			// 然后通过循环走同一层的其他节点。
			// 举个例子，比如上面图中，最开始的时候
			// 我们先选择2，然后沿着这个分支走下去，
			// 当回到当前分支的时候我们把2给移除，然后
			// 选择同一层的下一个3，沿着这个节点
			// 分支走下去……
			cur.remove(cur.size() - 1);
		}
	}

	public static void main(String[] args) {
		{
			int[] nums = { 2, 3, 6, 7 };
			int target = 7;
			List<List<Integer>> lists = combinationSum(nums, target);
			System.out.println(lists);
		}
		{
			int[] nums = { 2, 3, 6, 7 };
			int target = 7;
			List<List<Integer>> lists = combinationSum02(nums, target);
			System.out.println(lists);
		}
		{
			int[] nums = { 2, 3, 6, 7 };
			int target = 7;
			List<List<Integer>> lists = combinationSum03(nums, target);
			System.out.println(lists);
		}
	}
}
